////////////////////////////////////////////////
// DESCRIPTION:
//    Exclussive access locking mechanism
//
// Legal Notices:
//     Copyright (c) 2008, Telliam Consulting, LLC.
//     All rights reserved.
//
//     Redistribution and use in source and binary forms, with or without modification,
//     are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//
//     * Redistributions in binary form must reproduce the above copyright notice,
//       this list of conditions and the following disclaimer in the documentation
//       and/or other materials provided with the distribution.
//
//     * Neither the name of Telliam Consulting nor the names of its contributors
//       may be used to endorse or promote products derived from this software
//       without specific prior written permission.
//
// Disclaimer:
//     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
//     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
//     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
//     IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
//     INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
//     BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
//     DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
//     LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
//     OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
//     OF THE POSSIBILITY OF SUCH DAMAGE.  
//

#include <windows.h>
#include <FusionWare\Win32.h>

using namespace FusionWare::Threads;

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CExclusionLock::CExclusionLock()
{
  m_Count=0;
  // The event should be signaled since count is 0
  m_hZero = CreateEvent(NULL, TRUE, TRUE, NULL);
}

CExclusionLock::~CExclusionLock()
{
    CloseHandle(m_hZero);
}

// modifies the count in an atomic fashion. 
// grants exclusion to a single thread by not releasing 
// the lock on the internal count This will block any other
// threads from modifying the count until exclusion is released
void CExclusionLock::ModifyCount(DWORD AddValue,BOOL Exclusive) 
{ 
    // getting past this loop means either:
    // 1) m_Count is >=0 and thus exlcusion not yet applied nor requested
    // 2) Exclusion exists and this thread owns the exclusion lock.
    //
    // This loop handles the possible but unlikely scenario whereby 
    // the Wait for zero returns and some other thread is scheduled
    // that then alters the count before this function can lock it
    // down again.
    // The count can't be locked before the wait as the
    // wait is waiting for the count to change.
    do
    {  
        // lock Count before doing the test for exclusion
        m_csCount.Lock(); 
        if(Exclusive && m_Count>0)
        {
            // Release the lock and allow whoever has
            // incremented the count to decrement it back 
            // and wait for count to get to 0
            m_csCount.Unlock();
            WaitForSingleObject(m_hZero,INFINITE);
            // go back and try again...
            continue;
        }
        else
            break; // OK to continue as count is now safely locked

    // while(1) here is necessary to ensure loop does not exit 
    // unless there is a succesful Lock with the desired state
    }while(1);

    // Only one thread can get to this point at any time
    // as the above loop will exit with a lock on m_csCount
    // so if m_count<0 then this thread has exclusive access
    DWORD oldVal = m_Count;
    if(Exclusive || m_Count<0)
      m_Count -= AddValue;
    else
      m_Count += AddValue;

    // toggle state of Events on transitions to and from 0
    if(m_Count == 0)
        SetEvent(m_hZero);
    else
        if(m_Count==1 || m_Count==-1) // don't call ResetEvent unless needed
           ResetEvent(m_hZero);
  
   // if leaving Exclusive access then
   // do an extra unlock to replace
   // the one skipped below in a previous
   // call to this function
   if(oldVal<0 && m_Count==0)
     m_csCount.Unlock();

   // unlock the lock in this scope unless
   // granting exclusive access
   if(m_Count>=0 || oldVal<0)
       m_csCount.Unlock();
}

